package server;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * Created by swootton on 27/02/14.
 */
public class DownloadServlet extends MFHttpServlet {

    private int bytesTotal;
    private String UPLOAD_LOCATION = "";
    private ServletOutputStream servletOutputStream;
    private BufferedOutputStream bufferedOutputStream;

    private Connection connection;
    private Document requestXML;
    private Properties props;

    public DownloadServlet(){

    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        System.out.println("DownloadServlet.doPost()"+"\n");

        connection = (Connection) getServletContext().getAttribute("db_connection");

        requestXML = (Document) request.getAttribute("request_xml");

        //UPLOAD_LOCATION = (String) getServletContext().getAttribute("upload_location");
        UPLOAD_LOCATION = (String) request.getAttribute("upload_location");

        doDownloadRequest(request, response);

        connection = null;

    }

    public void doDownloadRequest(HttpServletRequest request, HttpServletResponse response) {

        System.out.println("doDownloadRequest()");

        /**
         * <?xml version="1.0" encoding="ISO-8859-1"?>
         *     <MFRequest>
                    <Request type='Download' data=''/>
                     <Track name='I Want You To Want Me' artist='Cheap Trick' />
                     <Track name='New Orleans' artist='Gillian' />
                     <Track name='Ooh La La' artist='Faces' />
                     <Track name='Queen Of Hearts' artist='Dave Edmunds' />
               </MFRequest>
         */

        NodeList nList = requestXML.getElementsByTagName("Track");
        Node nNode = nList.item(0);
        Element eElement = (Element) nNode;

        // TODO look-up file name from track and artist data
        // TODO therefore check if filename already exists (but diff name and artist)?
        // TODO generally rename file: (track - artist.mp3)

        StringBuilder query = new StringBuilder();

        query.append("SELECT mft.file ");
        query.append("FROM musicfinder.mf_tracks mft WHERE ");

        for (int i = 0; i < nList.getLength(); i++) {
            nNode = nList.item(i);

            if (nNode.getNodeType() == Node.ELEMENT_NODE) {
                eElement = (Element) nNode;
                eElement.normalize();
                String name = eElement.getAttribute("name");
                String artist = eElement.getAttribute("artist");

                query.append("mft.name='" + name  +"' AND mft.artist='"+artist+"'");

                if(nList.getLength() > i+1) {
                    query.append("OR ");
                }

                System.out.println("Download: " + name + " by " + artist + "\n");
            }
        }

        query.append(";");

        System.out.println("Download query: " + query.toString());
        System.out.println("connection: " + connection);

        String targetFileDownload = "";

        // use Type, otherwise:
        // javac -Xlint:unchecked server/MFServer.java
        // server/MFServer.java:372: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.Vector

        Vector<String> targetDownloads = new Vector<String>();
        int currDownload = 1;

        try {
            //Class.forName("com.mysql.jdbc.Driver");  //registering the jdbc driver here

            //db_con = DriverManager.getConnection(db_url, db_user, db_pwd);
            Statement db_st = connection.createStatement();
            ResultSet db_rs = db_st.executeQuery(query.toString());

            while (db_rs.next()) {

                //targetFileDownload = "track_"+currDownload+"_"+db_rs.getString(1);
                targetFileDownload = db_rs.getString(1);
                targetDownloads.add(targetFileDownload);
                //currDownload++;
            }
        }
        /*catch(ClassNotFoundException e1) {

            System.out.println(e1.getMessage());
            e1.printStackTrace();
        }*/
        catch (SQLException e2) {

            System.out.println(e2.getMessage());
            e2.printStackTrace();
        }

        initiateDownloads(targetDownloads, request, response);

        //for(int k = 0; k < targetDownloads.size(); k++) {
        //downloadFile(targetDownloads.get(k), request, response);
        //}
    }

    public void initiateDownloads(Vector <String> targetDownloads, HttpServletRequest request, HttpServletResponse response){

        System.out.println("initiateDownloads() UPLOAD_LOCATION "+UPLOAD_LOCATION);

        ServletContext context = getServletContext();
        //ServletContext context = getServletConfig().getServletContext();    // context.getContextPath() = /musicfinder

        // Size 130 kb (bits read = 133641, kilobits 130.5087890625)
        //String fileName = "dummy_download";
        //String fileName = context.getRealPath("dummy_download");

        //String musicDir = context.getContextPath() + "music/";

        String serverFileName = "";
        //int totalDownloadSize = 0;

        response.setHeader("Cache-Control", "no-cache");
        response.setContentType("text/html; charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("MFServer-Error", "false");

        Vector <File> targetDownloadFiles = new Vector<File>();

        //String UPLOAD_LOCATION = getServletContext().getAttribute("upload_location");

        int totalCompressedSize = 0;
        int totalUncompressedSize = 0;

        int k;

        for(k = 0; k < targetDownloads.size(); k++) {
            String targetFileName = targetDownloads.get(k);
            String targetFileSize = "";


            serverFileName = UPLOAD_LOCATION+targetFileName;
            //serverFileName = "/home/sam/mf_server_dump/music"+File.separator+targetFileName;
            //serverFileName = context.getRealPath("music/"+targetFileName);

            File file = new File(serverFileName);

            if(file.exists()) {     // WARNING: 404 track/file on server!!!!!!!!!!

                targetDownloadFiles.add(file);  // add file straight to ZipEntry ? ZipEntry requires file name anyway, not file
                totalUncompressedSize += file.length();
            } else {
                System.out.println("Download 404: " + targetFileName);
                // remove file from download list if its not found on server
                // we loop over this further down - should be safe.
                targetDownloads.remove(k);
            }
        }

        //response.setContentLength(totalUncompressedSize);

        //for(k = 0; k < targetDownloadFiles.size(); k++) {
        //downloadFile(targetDownloadFiles.get(k), totalDownloadSize, request, response);
        //}

        ZipFile tmpZip = null;
        //File tmpZip = null;

        // java.util.zip.ZipException: ZIP file must have at least one entry
        if(targetDownloads.size() == 0) {
            writeErrorResponse(request, response, "ALL_DOWNLOADS_404");
            // All files were 404 - eeeeeeeeeeeeek!!!!
        } else {

            try {
                // chmod u+rwx outfile.zip chmod o+rwx outfile.zip (allow tomcat process access)
                //String outFilename = "/home/sam/mf_server_dump/music_finder_downloads/download.zip";
                String outFilename = UPLOAD_LOCATION+"/download.zip";

                //tmpZip = new File(outFilename);

                //tmpZip = new ZipFile(new File(outFilename));

                //System.out.println("tmpZip "+tmpZip);

                ZipOutputStream zipOutputStream = null;


                // int unzippedFileSize = 0;

                // IMPORTANT must be outside of loop otherwise file gets recreated/overwritten
                zipOutputStream = new ZipOutputStream(new FileOutputStream(new File(outFilename)));
                //zipOutputStream = new ZipOutputStream(new FileOutputStream(outFilename));

                // Compress the files
                for (int i=0; i<targetDownloadFiles.size(); i++) {

                    String tmpFileName = targetDownloadFiles.get(i).getName();

                    //serverFileName = context.getRealPath("music/"+tmpFileName);
                    serverFileName = UPLOAD_LOCATION + tmpFileName;
                    //serverFileName = "/home/sam/mf_server_dump/music"+File.separator+tmpFileName;

                    FileInputStream zipInputStream = new FileInputStream(serverFileName);


                    ZipEntry zippedTrack = new ZipEntry(tmpFileName);   // Add ZIP entry to output stream.

                    // IMPORTANT: Begins writing a new ZIP file entry and positions the stream to the start of the entry data
                    // have to do this prior to writing stream.

                    zipOutputStream.putNextEntry(zippedTrack);

                    // Transfer bytes from the file to the ZIP file
                    int len;

                    byte[] buf = new byte[1024];

                    System.out.println("start write zip bytes");

                    while ((len = zipInputStream.read(buf)) > 0) {
                        zipOutputStream.write(buf, 0, len);
                        //totalCompressedSize+= len;
                    }

                    //totalCompressedSize += zippedTrack.getCompressedSize();

                    System.out.println("zippedTrack.getName() "+zippedTrack.getName());
                    System.out.println("zippedTrack.getCompressedSize() "+zippedTrack.getCompressedSize());
                }

                // Complete the ZIP file
                zipOutputStream.close();

                //int zippedFileSize = (int) tmpZip.length();
                //System.out.println("zippedFileSize "+zippedFileSize);

            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("IOException "+e.toString());
            }

            //downloadZipFile("/home/sam/mf_server_dump/music_finder_downloads/download.zip", totalCompressedSize, totalUncompressedSize, request, response);
            downloadZipFile(UPLOAD_LOCATION+"download.zip", totalCompressedSize, totalUncompressedSize, request, response);

            //downloadZipFile(tmpZip, totalCompressedSize, totalUncompressedSize, request, response);

            // Number of bytes in the file (133641)
            //System.out.println("serverFileName " + serverFileName);
            //System.out.println("file.length() "+file.length());

            //response.setContentType((mimetype != null) ? mimetype : "application/octet-stream");

            //response.setContentLength((int)file.length());

            // TODO could just use param now instead of split()
            // TODO just grab file name from db and concat on to context path.

        }   // End else for all files 404
    }

    //public void downloadZipFile(ZipFile zipFile, int totalCompressedSize, int totalUncompressedSize, HttpServletRequest request, HttpServletResponse response) {
    public void downloadZipFile(String zipFile, int totalCompressedSize, int totalUncompressedSize, HttpServletRequest request, HttpServletResponse response) {

        System.out.println("downloadZipFile " + zipFile);

        try {
            servletOutputStream = response.getOutputStream();
            bufferedOutputStream = new BufferedOutputStream(servletOutputStream);
            bytesTotal = 0;
        } catch (IOException e) {
            System.out.println(e.toString());
            e.printStackTrace();
        }

        response.setHeader("Content-Disposition", "attachment; filename=\"" + zipFile + "\"");

        //String fileLength = ""+zipFileName.length();

        //File tmpZip = new File(zipFileName);

        //System.out.println("tmpZip.length() "+tmpZip.length());
        response.setHeader("Compressed-File-size", ""+totalCompressedSize);
        response.setHeader("Uncompressed-File-size", ""+totalUncompressedSize);
        response.setHeader("File-name", "outfile.zip");  //TODO change

        //Long tmpLong = new Long(tmpZip.length());

        //response.setContentLength(totalUncompressedSize);
        //response.setContentLength((int) tmpZip.length());

        int BYTES_DOWNLOAD = 1024;
        int read=0;
        byte[] bytes = new byte[BYTES_DOWNLOAD];

        DataInputStream dataInputStream = null;

        FileInputStream fileInputStream = null;

        //int totalDownloadSize = (int) tmpZip.length();

        try {

            // file.length() 133641
            // fileName /usr/share/tomcat6/webapps/musicfinder/dummy_download
            // Have to use FileInputStream

            System.out.println("zipFile "+ zipFile);

            File f = new File(zipFile);
            System.out.println("zipFile.getBytes() "+ zipFile.getBytes());

            response.setContentLength((int)f.length());   // correct for downloading
            //response.setContentLength(totalUncompressedSize);   // correct for uncompressing?
            // no appears content len is good for both downloading and uncompressing!!!

            fileInputStream = new FileInputStream(f);

            System.out.println("fileInputStream "+ fileInputStream);
            System.out.println("servletOutputStream "+ servletOutputStream);

            while((read = fileInputStream.read(bytes)) != -1)
            {
                //bufferedOutputStream.write(bytes, 0, read);
                servletOutputStream.write(bytes);
                bytesTotal += read; // content length should be this?

                //outputStream.write(read);
                //outputStream.write(bytes, 0, read);
            }

            System.out.println("bytesTotal"+bytesTotal);

            System.out.println("servletOutputStream.bytes written: "+bytesTotal);
            System.out.println("totalCompressedSize " + totalCompressedSize);
            System.out.println("totalUncompressedSize " + totalUncompressedSize);

            //bufferedOutputStream.flush();
            //bufferedOutputStream.close();

            //if(bytesTotal == totalCompressedSize){
            System.out.println("servletOutputStream.flush()");
            System.out.println("servletOutputStream.close()");
            servletOutputStream.flush();
            servletOutputStream.close();
            //}

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void downloadFile(File file, int totalDownloadSize, HttpServletRequest request, HttpServletResponse response) {

        System.out.println("downloadFile "+file.getName());

        //ServletOutputStream servletOutputStream = null;
        //BufferedOutputStream bufferedOutputStream = null;

        if(servletOutputStream == null){
            try {
                System.out.println("new Streams");
                servletOutputStream = response.getOutputStream();
                bufferedOutputStream = new BufferedOutputStream(servletOutputStream);
                bytesTotal = 0;
            } catch (IOException e) {
                System.out.println(e.toString());
                e.printStackTrace();
            }
        }

        ServletContext context = getServletContext();

        String serverFileName = UPLOAD_LOCATION + file.getName();
        //String serverFileName = context.getRealPath("/home/sam/mf_server_dump/music/"+file.getName());   // have to do again for some reason, loses context done previously

        String mimetype = context.getMimeType(serverFileName);

        System.out.println("mimetype " + mimetype);

        response.setHeader("Content-Disposition", "attachment; filename=\"" + serverFileName + "\"");

        //String tidyFileName = serverFileName.split(context.getContextPath()+"/")[2];
        String tidyFileName = file.getName();
        String fileLength = ""+file.length();

        System.out.println("tidyFileName " +tidyFileName);
        response.setHeader("File-size", fileLength);
        response.setHeader("File-name",tidyFileName);

        //response.setContentLength((int) file.length());

        int BYTES_DOWNLOAD = 1024;
        int read=0;
        byte[] bytes = new byte[BYTES_DOWNLOAD];


        DataInputStream dataInputStream = null;

        // /usr/share/tomcat6/webapps/musicfinder/dummy_download
        System.out.println("");


        //InputStream inputStream = context.getResourceAsStream(fileName);
        FileInputStream fileInputStream = null;

        try {

            // file.length() 133641
            // fileName /usr/share/tomcat6/webapps/musicfinder/dummy_download
            // Have to use FileInputStream

            System.out.println("serverFileName "+ serverFileName);

            fileInputStream = new FileInputStream(serverFileName);

            while((read = fileInputStream.read(bytes)) != -1)
            {
                //bufferedOutputStream.write(bytes, 0, read);
                servletOutputStream.write(bytes);

                bytesTotal += read;

                //outputStream.write(read);
                //outputStream.write(bytes, 0, read);
            }

            System.out.println("bytesTotal " + bytesTotal);
            System.out.println("totalDownloadSize " + totalDownloadSize);

            //bufferedOutputStream.flush();
            //bufferedOutputStream.close();

            if(bytesTotal == totalDownloadSize){
                System.out.println("servletOutputStream.flush()");
                System.out.println("servletOutputStream.close()");
                servletOutputStream.flush();
                servletOutputStream.close();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
